<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Takumi Web Client Demo</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family:
          -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        min-height: 100vh;
        padding: 20px;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
      }

      h1 {
        color: white;
        text-align: center;
        margin-bottom: 30px;
        font-size: 2.5em;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
      }

      .connection-status {
        text-align: center;
        margin-bottom: 20px;
      }

      .status-badge {
        display: inline-block;
        padding: 8px 16px;
        border-radius: 20px;
        font-weight: bold;
        color: white;
        transition: all 0.3s ease;
      }

      .status-connected {
        background-color: #4caf50;
      }

      .status-connecting {
        background-color: #ff9800;
        animation: pulse 1.5s infinite;
      }

      .status-disconnected {
        background-color: #f44336;
      }

      @keyframes pulse {
        0% {
          opacity: 1;
        }
        50% {
          opacity: 0.6;
        }
        100% {
          opacity: 1;
        }
      }

      .main-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 20px;
        margin-bottom: 20px;
      }

      .panel {
        background: white;
        border-radius: 10px;
        padding: 20px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
      }

      .panel h2 {
        color: #333;
        margin-bottom: 15px;
        padding-bottom: 10px;
        border-bottom: 2px solid #667eea;
      }

      .control-group {
        margin-bottom: 15px;
      }

      .control-group label {
        display: block;
        margin-bottom: 5px;
        color: #666;
        font-weight: 500;
      }

      .control-group input,
      .control-group textarea,
      .control-group select {
        width: 100%;
        padding: 8px 12px;
        border: 1px solid #ddd;
        border-radius: 5px;
        font-size: 14px;
        transition: border-color 0.3s ease;
      }

      .control-group input:focus,
      .control-group textarea:focus,
      .control-group select:focus {
        outline: none;
        border-color: #667eea;
      }

      .control-group textarea {
        min-height: 100px;
        resize: vertical;
      }

      .button {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-weight: bold;
        transition:
          transform 0.2s ease,
          box-shadow 0.2s ease;
        margin-right: 10px;
        margin-bottom: 10px;
      }

      .button:hover {
        transform: translateY(-2px);
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
      }

      .button:active {
        transform: translateY(0);
      }

      .button:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      .button.secondary {
        background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
      }

      .button.danger {
        background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
      }

      .messages-container {
        grid-column: 1 / -1;
      }

      .messages-log {
        height: 400px;
        overflow-y: auto;
        border: 1px solid #ddd;
        border-radius: 5px;
        padding: 10px;
        background: #f9f9f9;
        font-family: 'Monaco', 'Menlo', monospace;
        font-size: 12px;
      }

      .message-item {
        margin-bottom: 10px;
        padding: 8px;
        border-radius: 5px;
        animation: slideIn 0.3s ease;
      }

      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translateX(-20px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }

      .message-request {
        background: #e3f2fd;
        border-left: 3px solid #2196f3;
      }

      .message-response {
        background: #e8f5e9;
        border-left: 3px solid #4caf50;
      }

      .message-event {
        background: #fff3e0;
        border-left: 3px solid #ff9800;
      }

      .message-error {
        background: #ffebee;
        border-left: 3px solid #f44336;
        color: #c62828;
      }

      .message-timestamp {
        color: #999;
        font-size: 10px;
      }

      .quick-actions {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-top: 15px;
      }

      .loading {
        display: inline-block;
        width: 20px;
        height: 20px;
        border: 3px solid #f3f3f3;
        border-top: 3px solid #667eea;
        border-radius: 50%;
        animation: spin 1s linear infinite;
        margin-left: 10px;
      }

      @keyframes spin {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>🚀 Takumi Web Client Demo</h1>

      <div class="connection-status">
        <span id="status" class="status-badge status-disconnected"
          >Disconnected</span
        >
      </div>

      <div class="main-grid">
        <div class="panel">
          <h2>Connection & Initialize</h2>
          <div class="control-group">
            <label for="wsUrl">WebSocket URL:</label>
            <input type="text" id="wsUrl" value="ws://localhost:3000/ws" />
          </div>
          <div class="control-group">
            <label for="cwd">Working Directory:</label>
            <input
              type="text"
              id="cwd"
              placeholder="/path/to/project"
              value="/tmp"
            />
          </div>
          <button id="connectBtn" class="button">Connect</button>
          <button id="disconnectBtn" class="button danger" disabled>
            Disconnect
          </button>
          <button id="initializeBtn" class="button secondary" disabled>
            Initialize
          </button>
        </div>

        <div class="panel">
          <h2>Send Message</h2>
          <div class="control-group">
            <label for="message">Message:</label>
            <textarea id="message" placeholder="Enter your message here...">
Hello, how can I help you today?</textarea
            >
          </div>
          <div class="control-group">
            <label for="sessionId">Session ID (optional):</label>
            <input
              type="text"
              id="sessionId"
              placeholder="Leave empty for new session"
            />
          </div>
          <div class="control-group">
            <label>
              <input type="checkbox" id="planMode" />
              Plan Mode
            </label>
          </div>
          <button id="sendBtn" class="button" disabled>Send Message</button>
          <button id="cancelBtn" class="button danger" disabled>Cancel</button>
        </div>

        <div class="panel">
          <h2>Quick Actions</h2>
          <div class="quick-actions">
            <button class="button secondary" onclick="getStatus()" disabled>
              Get Status
            </button>
            <button
              class="button secondary"
              onclick="getAllSessions()"
              disabled
            >
              Get Sessions
            </button>
            <button class="button secondary" onclick="getModels()" disabled>
              Get Models
            </button>
            <button
              class="button secondary"
              onclick="getOutputStyles()"
              disabled
            >
              Output Styles
            </button>
            <button
              class="button secondary"
              onclick="getSlashCommands()"
              disabled
            >
              Slash Commands
            </button>
            <button class="button secondary" onclick="getMcpStatus()" disabled>
              MCP Status
            </button>
          </div>
        </div>

        <div class="panel">
          <h2>Custom Request</h2>
          <div class="control-group">
            <label for="method">Method:</label>
            <select id="method">
              <option>initialize</option>
              <option>send</option>
              <option>cancel</option>
              <option>addMessages</option>
              <option>getStatus</option>
              <option>setConfig</option>
              <option>getOutputStyles</option>
              <option>getAllSessions</option>
              <option>resumeSession</option>
              <option>getModels</option>
              <option>getSlashCommands</option>
              <option>getSlashCommand</option>
              <option>executeSlashCommand</option>
              <option>query</option>
              <option>getPaths</option>
              <option>compact</option>
              <option>getMcpStatus</option>
              <option>reconnectMcpServer</option>
              <option>clearContext</option>
            </select>
          </div>
          <div class="control-group">
            <label for="params">Parameters (JSON):</label>
            <textarea id="params" placeholder='{"key": "value"}'>{}</textarea>
          </div>
          <button id="customRequestBtn" class="button" disabled>
            Send Request
          </button>
        </div>

        <div class="panel messages-container">
          <h2>Messages Log</h2>
          <button class="button secondary" onclick="clearLog()">
            Clear Log
          </button>
          <div id="messages" class="messages-log"></div>
        </div>
      </div>
    </div>

    <script>
      // WebSocket Transport implementation for browser
      class WebSocketTransport {
        constructor(url) {
          this.url = url;
          this.ws = null;
          this.state = 'disconnected';
          this.messageHandlers = [];
          this.errorHandlers = [];
          this.closeHandlers = [];
          this.messageBuffer = [];
          this.reconnectInterval = 1000;
          this.maxReconnectInterval = 30000;
          this.shouldReconnect = true;
        }

        connect() {
          return new Promise((resolve, reject) => {
            try {
              this.state = 'connecting';
              this.ws = new WebSocket(this.url);

              this.ws.onopen = () => {
                this.state = 'connected';
                this.reconnectInterval = 1000;
                this.flushBuffer();
                resolve();
              };

              this.ws.onmessage = (event) => {
                try {
                  const message = JSON.parse(event.data);
                  this.messageHandlers.forEach((handler) => handler(message));
                } catch (error) {
                  console.error('Failed to parse message:', error);
                  this.errorHandlers.forEach((handler) => handler(error));
                }
              };

              this.ws.onerror = (error) => {
                this.state = 'error';
                this.errorHandlers.forEach((handler) => handler(error));
              };

              this.ws.onclose = () => {
                this.state = 'disconnected';
                this.closeHandlers.forEach((handler) => handler());

                if (this.shouldReconnect) {
                  setTimeout(() => {
                    this.connect().catch(console.error);
                    this.reconnectInterval = Math.min(
                      this.reconnectInterval * 2,
                      this.maxReconnectInterval,
                    );
                  }, this.reconnectInterval);
                }
              };
            } catch (error) {
              this.state = 'error';
              reject(error);
            }
          });
        }

        flushBuffer() {
          const messages = [...this.messageBuffer];
          this.messageBuffer = [];
          messages.forEach((message) => {
            this.send(message).catch(console.error);
          });
        }

        isConnected() {
          return (
            this.state === 'connected' &&
            this.ws &&
            this.ws.readyState === WebSocket.OPEN
          );
        }

        onMessage(handler) {
          this.messageHandlers.push(handler);
        }

        onError(handler) {
          this.errorHandlers.push(handler);
        }

        onClose(handler) {
          this.closeHandlers.push(handler);
        }

        async send(message) {
          if (this.isConnected()) {
            this.ws.send(JSON.stringify(message));
          } else {
            this.messageBuffer.push(message);
            throw new Error('WebSocket is not connected');
          }
        }

        async close() {
          this.shouldReconnect = false;
          if (this.ws) {
            this.ws.close();
            this.ws = null;
          }
          this.state = 'closed';
        }
      }

      // Message Bus implementation for browser
      class MessageBus {
        constructor() {
          this.transport = null;
          this.pendingRequests = new Map();
          this.messageHandlers = new Map();
          this.eventHandlers = new Map();
        }

        setTransport(transport) {
          this.transport = transport;
          transport.onMessage((message) => {
            this.handleIncomingMessage(message);
          });
        }

        generateId() {
          return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        }

        async request(method, params, options = {}) {
          if (!this.transport || !this.transport.isConnected()) {
            throw new Error('Transport is not connected');
          }

          const id = this.generateId();
          const message = {
            type: 'request',
            id,
            method,
            params,
            timestamp: Date.now(),
          };

          return new Promise((resolve, reject) => {
            const timeout = options.timeout || 30000;

            const timeoutId = setTimeout(() => {
              this.pendingRequests.delete(id);
              reject(new Error(`Request timeout: ${method}`));
            }, timeout);

            this.pendingRequests.set(id, {
              resolve,
              reject,
              timeoutId,
            });

            this.transport.send(message).catch((error) => {
              clearTimeout(timeoutId);
              this.pendingRequests.delete(id);
              reject(error);
            });
          });
        }

        handleIncomingMessage(message) {
          logMessage(message);

          switch (message.type) {
            case 'response':
              this.handleResponse(message);
              break;
            case 'event':
              this.handleEvent(message);
              break;
            case 'request':
              // Handle incoming requests from server
              this.handleRequest(message);
              break;
          }
        }

        handleResponse(message) {
          const pending = this.pendingRequests.get(message.id);
          if (pending) {
            clearTimeout(pending.timeoutId);
            this.pendingRequests.delete(message.id);

            if (message.error) {
              pending.reject(new Error(message.error.message || message.error));
            } else {
              pending.resolve(message.result);
            }
          }
        }

        handleEvent(message) {
          const handlers = this.eventHandlers.get(message.event);
          if (handlers) {
            handlers.forEach((handler) => handler(message.data));
          }
        }

        handleRequest(message) {
          const handler = this.messageHandlers.get(message.method);
          if (handler) {
            handler(message.params)
              .then((result) => {
                this.transport.send({
                  type: 'response',
                  id: message.id,
                  result,
                  timestamp: Date.now(),
                });
              })
              .catch((error) => {
                this.transport.send({
                  type: 'response',
                  id: message.id,
                  error: { message: error.message },
                  timestamp: Date.now(),
                });
              });
          }
        }

        onEvent(event, handler) {
          if (!this.eventHandlers.has(event)) {
            this.eventHandlers.set(event, []);
          }
          this.eventHandlers.get(event).push(handler);
        }

        registerHandler(method, handler) {
          this.messageHandlers.set(method, handler);
        }
      }

      // Global variables
      let transport = null;
      let messageBus = null;
      let currentSessionId = null;

      // UI Elements
      const statusEl = document.getElementById('status');
      const connectBtn = document.getElementById('connectBtn');
      const disconnectBtn = document.getElementById('disconnectBtn');
      const initializeBtn = document.getElementById('initializeBtn');
      const sendBtn = document.getElementById('sendBtn');
      const cancelBtn = document.getElementById('cancelBtn');
      const customRequestBtn = document.getElementById('customRequestBtn');
      const messagesEl = document.getElementById('messages');

      // Update UI based on connection state
      function updateConnectionStatus(state) {
        statusEl.textContent = state.charAt(0).toUpperCase() + state.slice(1);
        statusEl.className = `status-badge status-${state}`;

        const isConnected = state === 'connected';
        connectBtn.disabled = isConnected;
        disconnectBtn.disabled = !isConnected;
        initializeBtn.disabled = !isConnected;
        sendBtn.disabled = !isConnected;
        cancelBtn.disabled = !isConnected;
        customRequestBtn.disabled = !isConnected;

        // Enable/disable quick action buttons
        document.querySelectorAll('.quick-actions button').forEach((btn) => {
          btn.disabled = !isConnected;
        });
      }

      // Log messages to the UI
      function logMessage(message, type = null) {
        const messageEl = document.createElement('div');
        messageEl.className = 'message-item';

        if (!type) {
          type = message.type || 'info';
        }

        switch (type) {
          case 'request':
            messageEl.className += ' message-request';
            break;
          case 'response':
            messageEl.className += ' message-response';
            break;
          case 'event':
            messageEl.className += ' message-event';
            break;
          case 'error':
            messageEl.className += ' message-error';
            break;
        }

        const timestamp = new Date().toLocaleTimeString();
        messageEl.innerHTML = `
                <div class="message-timestamp">${timestamp}</div>
                <pre>${JSON.stringify(message, null, 2)}</pre>
            `;

        messagesEl.appendChild(messageEl);
        messagesEl.scrollTop = messagesEl.scrollHeight;
      }

      function clearLog() {
        messagesEl.innerHTML = '';
      }

      // Connect to WebSocket
      async function connect() {
        try {
          const url = document.getElementById('wsUrl').value;
          transport = new WebSocketTransport(url);
          messageBus = new MessageBus();

          transport.onClose(() => {
            updateConnectionStatus('disconnected');
          });

          transport.onError((error) => {
            logMessage({ error: error.message }, 'error');
          });

          // Set up event handlers
          messageBus.onEvent('connected', (data) => {
            logMessage({ event: 'connected', data }, 'event');
          });

          messageBus.onEvent('message', (data) => {
            logMessage({ event: 'message', data }, 'event');
          });

          messageBus.onEvent('textDelta', (data) => {
            logMessage({ event: 'textDelta', data }, 'event');
          });

          messageBus.onEvent('chunk', (data) => {
            // Only log a summary for chunks to avoid spam
            console.log('Chunk received:', data);
          });

          // Handle tool approval requests
          messageBus.registerHandler('toolApproval', async (params) => {
            const approved = confirm(
              `Tool approval requested:\n${params.toolUse.name}\n\nApprove?`,
            );
            return { approved };
          });

          updateConnectionStatus('connecting');
          await transport.connect();
          messageBus.setTransport(transport);
          updateConnectionStatus('connected');

          logMessage({ status: 'Connected to WebSocket server' }, 'event');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
          updateConnectionStatus('disconnected');
        }
      }

      // Disconnect from WebSocket
      async function disconnect() {
        try {
          if (transport) {
            await transport.close();
            transport = null;
            messageBus = null;
          }
          updateConnectionStatus('disconnected');
          logMessage({ status: 'Disconnected from server' }, 'event');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Initialize session
      async function initialize() {
        try {
          const cwd = document.getElementById('cwd').value;
          const sessionId =
            document.getElementById('sessionId').value || undefined;

          const result = await messageBus.request('session.initialize', {
            cwd,
            sessionId,
          });
          logMessage({ method: 'initialize', result }, 'response');

          if (result.success) {
            alert(
              `Initialized successfully!\n${result.data.productName} v${result.data.version}`,
            );
          }
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Send message
      async function sendMessage() {
        try {
          const message = document.getElementById('message').value;
          const cwd = document.getElementById('cwd').value;
          const sessionId =
            document.getElementById('sessionId').value || undefined;
          const planMode = document.getElementById('planMode').checked;

          if (sessionId) {
            currentSessionId = sessionId;
          }

          const result = await messageBus.request('session.send', {
            message,
            cwd,
            sessionId,
            planMode,
          });

          logMessage({ method: 'send', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Cancel current operation
      async function cancel() {
        try {
          const cwd = document.getElementById('cwd').value;
          const sessionId =
            currentSessionId || document.getElementById('sessionId').value;

          if (!sessionId) {
            alert('No session ID available to cancel');
            return;
          }

          const result = await messageBus.request('session.cancel', { cwd, sessionId });
          logMessage({ method: 'cancel', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Custom request
      async function sendCustomRequest() {
        try {
          const method = document.getElementById('method').value;
          const paramsText = document.getElementById('params').value;
          const params = JSON.parse(paramsText);

          // Add cwd to params if not present and method requires it
          if (
            !params.cwd &&
            [
              'initialize',
              'send',
              'cancel',
              'getStatus',
              'setConfig',
              'getOutputStyles',
              'getAllSessions',
              'resumeSession',
              'getModels',
              'getSlashCommands',
              'getSlashCommand',
              'executeSlashCommand',
              'query',
              'getPaths',
              'compact',
              'getMcpStatus',
              'reconnectMcpServer',
            ].includes(method)
          ) {
            params.cwd = document.getElementById('cwd').value;
          }

          const result = await messageBus.request(method, params);
          logMessage({ method, result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Quick action functions
      async function getStatus() {
        try {
          const cwd = document.getElementById('cwd').value;
          const sessionId =
            currentSessionId ||
            document.getElementById('sessionId').value ||
            'default';
          const result = await messageBus.request('status.get', {
            cwd,
            sessionId,
          });
          logMessage({ method: 'getStatus', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      async function getAllSessions() {
        try {
          const cwd = document.getElementById('cwd').value;
          const result = await messageBus.request('sessions.list', { cwd });
          logMessage({ method: 'getAllSessions', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      async function getModels() {
        try {
          const cwd = document.getElementById('cwd').value;
          const result = await messageBus.request('models.list', { cwd });
          logMessage({ method: 'getModels', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      async function getOutputStyles() {
        try {
          const cwd = document.getElementById('cwd').value;
          const result = await messageBus.request('outputStyles.list', { cwd });
          logMessage({ method: 'getOutputStyles', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      async function getSlashCommands() {
        try {
          const cwd = document.getElementById('cwd').value;
          const result = await messageBus.request('slashCommand.list', { cwd });
          logMessage({ method: 'getSlashCommands', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      async function getMcpStatus() {
        try {
          const cwd = document.getElementById('cwd').value;
          const result = await messageBus.request('mcp.getStatus', { cwd });
          logMessage({ method: 'getMcpStatus', result }, 'response');
        } catch (error) {
          logMessage({ error: error.message }, 'error');
        }
      }

      // Event listeners
      connectBtn.addEventListener('click', connect);
      disconnectBtn.addEventListener('click', disconnect);
      initializeBtn.addEventListener('click', initialize);
      sendBtn.addEventListener('click', sendMessage);
      cancelBtn.addEventListener('click', cancel);
      customRequestBtn.addEventListener('click', sendCustomRequest);

      // Initialize on page load
      updateConnectionStatus('disconnected');
    </script>
  </body>
</html>
